package com.util;

import com.annotation.MyBean;
import com.annotation.MyDateFormat;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

public class ReflectUtil {

    /**
     * 把HashMap的key与JavaBean的属性对应，将key对应的Bean属性值赋到javaBean中
     *  步骤
     *  1.得到类中的所有只有一个参数的set方法
     *  2.获得方法名称(去掉set后，将set后的首字母改为小写)
     *  3.根据第二步得到的方法名(属性名) 做为key在map中取出相应的值
     *  4.调用method.invoke方法，将map 的value做为set方法的参数传入到set方法中
     *      4.1 当map中取出来的值与set方法要求的参数类型不匹配时，尽可能利用以前所学的知识把map的值转换成对应的参数类型，进行传递
     *                如:  map.put("age","80") --->public setAge(int age);
     * @param   map 传入map对象（对应于数据库表的一条记录）
     * @param   cls 对应于某个JavaBean的class
     * @return 返回实例化后且映射 的JavaBean对象
     */
    public static <T> T mapValueToBean(Map<String,Object> map,Class<T> cls) throws IllegalAccessException, InstantiationException {

         T t = cls.newInstance();//实例化参数对应的对象

         Method[] methods = cls.getMethods();

         for(Method m:methods){
             String methodName = m.getName();//得到方法名
             Class[] parameterTypes =   m.getParameterTypes();//得到方法的参数列表

             if (methodName.startsWith("set") && parameterTypes.length==1){ //判断方法名称为set且只有一个参数

                 //输出set方法的参数类型以及map中取出的值的实际类型
                 String paramTypeName = parameterTypes[0].getName();//得到set方法的参数类型(字符串表示)

                 //截取set之后的方法名称(属性名)
                  methodName =  methodName.substring(3).toLowerCase();

                  //根据methodName 从形参map中取出对应的值
                  Object mapValue =  map.get(methodName);

                  if (mapValue!=null){
                      
                      try {
                          String mapValueTypeName = mapValue.getClass().getName();//得到map中的值的实际类型(字符串)
                          if (paramTypeName.equals(mapValueTypeName)) {//当方法的形参类型与map中的值的类型完全一致时，直接调用invoke
                              m.invoke(t, mapValue);// 此处捕获异常的目的，尽可能的将map中的值传递到setXX方法中，即使某一个set方法调用失败，不影响后续set方法的执行   setAge,setName,setHeight
                          }else if (paramTypeName.equals("int")|| paramTypeName.equals("java.lang.Integer")){  //判断方法参数 可能出现的各种java类型，尽可能的将map对应的值类型转换成形参对应的类型
                                //mapValue有可能是String类型，也有可能是int,也有可能是BigDecimal类型
                              if (mapValueTypeName.equals("java.lang.String") && String.valueOf(mapValue).matches("[0-9]+")){

                                  m.invoke(t,Integer.parseInt( String.valueOf(mapValue)));
                              }else if (mapValueTypeName.equals("java.math.BigDecimal")){
                                  BigDecimal bigDecimal = (BigDecimal)mapValue;
                                  m.invoke(t,bigDecimal.intValue());
                              }

                          }else if (paramTypeName.equals("java.util.Date")){

                              //当map中取出来的值类型为String时，就要将String 转换为java.util.date类型
                              SimpleDateFormat sdf =null;

                              String filedName =  m.getName().substring(3,4).toLowerCase()+m.getName().substring(4);
                              try {
                                  Field field = cls.getDeclaredField(filedName);

                                  //判断filed是否存在MyBean的属性，存在就递归把map中的值映射到DeptBean中
                                  //判断属性上是否存在MyDateFormate注解，存在则用MyDateFormat注解的format属性值做为日期模式
                                  if (field.isAnnotationPresent(MyDateFormat.class)) {
                                        MyDateFormat myDateFormat = (MyDateFormat)field.getAnnotation(MyDateFormat.class);
                                          sdf = new SimpleDateFormat(myDateFormat.format());
                                  }else{
                                      sdf  = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
                                  }

                                 Date d  =  sdf.parse(String.valueOf(mapValue));
                                 m.invoke(t,d);//传入转换后的日期类型
                              } catch (Exception e) {
                                  e.printStackTrace();
                              }

                          }else  if (paramTypeName.equals("double")||paramTypeName.equals("java.lang.Double")) {    //补充更多可能出现的类型不一致的数据转换。。
                                //判断map的值类型为string或BigDicmal或float,int,short等情况
                                //map.put(sal,10)   map.put(sal,"100.9")  map.put.sal(sal,(byte)10)
                                if (mapValueTypeName.equals("java.math.BigDecimal")){
                                    BigDecimal bigDecimal = (BigDecimal)mapValue;
                                    m.invoke(t,bigDecimal.doubleValue());
                                }//当值的类型为int,float,byte,short等类型时不能直接做为setXX(Double d) 方法参数传入进去，我们可以先将mapValue转换为String,判断String的格式是否能够转换为Double
                                else{
                                    String sValue = String.valueOf(mapValue);//把mapValue转换为字符串
                                    if (sValue.matches("[0-9]+([.][0-9]+)?")){//判断字符串是否为可转换为double类型的数字格式
                                        m.invoke(t,Double.parseDouble(sValue));//把字符串转换为Double做为set方法的参数
                                    }
                                }

                          }
                      } catch (InvocationTargetException e) {
                          e.printStackTrace();
                      } catch (IllegalArgumentException e){
                          e.printStackTrace();
                      }
                  }else {//else if (paramTypeName.startsWith("com.bean")){//代表某个类（EmpBean）的属性类型为com.bean.XXX类型

                      //根据方法名称得到对应的属性  setDetpBean  -->deptBean(String)
                      String filedName =  m.getName().substring(3,4).toLowerCase()+m.getName().substring(4);
                      try {
                          Field field =  cls.getDeclaredField(filedName);
                          //判断filed是否存在MyBean的属性，存在就递归把map中的值映射到DeptBean中

                          if(field.isAnnotationPresent(MyBean.class)) {
                              //当方法的参数类型为com.bean包中的类型时
                              try {
                                  Object object = mapValueToBean(map, Class.forName(paramTypeName)); //com.baidu.bean.DeptBean
                                  

                                  try {
                                      m.invoke(t, object);//相当 于调用setDeptBean(DeptBean obj) 方法将递归映射的结果做为方法参数
                                  } catch (InvocationTargetException e) {
                                      e.printStackTrace();
                                  }

                              } catch (ClassNotFoundException e) {
                                  e.printStackTrace();
                              }
                          }
                      } catch (NoSuchFieldException e) {
                          e.printStackTrace();

                      }
                  }
             }
         }
         return t;
    }
}
